import functools
import inspect
import logging
import asyncio
import os
from urllib import parse
from aiohttp import web
from apis import APIError


def get(path):
    '''
    Define decorator @get('/path')
    '''
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kw):
            return func(*args, **kw)
        wrapper.__method__ = 'GET'
        wrapper.__route__ = path
        return wrapper
    return decorator


def post(path):
    '''
    Define decorator @post('/path')
    '''
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kw):
            return func(*args, **kw)
        wrapper.__method__ = 'POST'
        wrapper.__route__ = path
        return wrapper
    return decorator


# 使用inspect模块，检查视图函数的参数

# inspect.Parameter.kind 类型：
# POSITIONAL_ONLY          位置参数
# KEYWORD_ONLY             命名关键词参数
# VAR_POSITIONAL           可选参数 *args
# VAR_KEYWORD              关键词参数 **kw
# POSITIONAL_OR_KEYWORD    位置或必选参数
# 获取请求的命名关键字参数
def get_required_kw_arg(fn):
    args = []
    params = inspect.signature(fn).parameters
    for name, param in params.items():
        if param.kind == inspect.Parameter.KEYWORD_ONLY and param.default == inspect.Parameter.empty:
            args.append(name)
    return tuple(args)


# 获取命名关键字参数
def get_named_kw_args(fn):
    args = []
    params = inspect.signature(fn).parameters
    for name, param in params.items():
        if param.kind == inspect.Parameter.KEYWORD_ONLY:
            args.append(name)
    return tuple(args)


# 判断是否有命名关键字参数
def has_named_kw_arg(fn):
    params = inspect.signature(fn).parameters
    for name, param in params.items():
        if param.kind == inspect.Parameter.KEYWORD_ONLY:
            return True
    return False


# 是否有关键字参数
def has_var_kw_arg(fn):
    params = inspect.signature(fn).parameters
    for name, param in params.items():
        if param.kind == inspect.Parameter.VAR_KEYWORD:  # 自定义关键字参数
            return True
    return False


# inspect.Parameter.VAR_POSITIONAL 自定义参数(也就是可变参数)
# 是否有参数名为 request 的参数
def has_request_arg(fn):
    sig = inspect.signature(fn)
    params = sig.parameters
    found = False
    for name, param in params.items():
        if name == 'request':
            found = True
            continue
        VAR_POSITIONAL = inspect.Parameter.VAR_POSITIONAL  # 可变参数 *args
        KEYWORD_ONLY = inspect.Parameter.KEYWORD_ONLY  # 命名关键字参数 (*,name,city)
        VAR_KEYWORD = inspect.Parameter.VAR_KEYWORD  # 关键字参数 (**kw)
        # 如果 request不是命名关键字最后一个参数,则抛出错误
        if found and (param.kind != VAR_POSITIONAL and param.kind != KEYWORD_ONLY and param.kind != VAR_KEYWORD):
            raise ValueError('request parameter must be the last named parameter in function: %s%s' % (
                fn.__name__, str(sig)))
    return found


# 定义RequestHandler从视图函数中分析其需要接受的参数，从web.Request中获取必要的参数
# 调用视图函数，然后把结果转换为web.Response对象，符合aiohttp框架要求
class RequestHandler(object):
    """docstring for RequestHandler"""

    def __init__(self, app, fn):
        self._app = app
        self._func = fn
        self._has_request_arg = has_request_arg(fn)  # 是否有参数名为 request 的参数
        self._has_var_kw_arg = has_var_kw_arg(fn)  # 是否有关键字参数
        self._has_named_kw_arg = has_named_kw_arg(fn)  # 是否有命名关键字参数
        self._named_kw_args = get_named_kw_args(fn)
        self._required_kw_arg = get_required_kw_arg(fn)
        logging.info("RequestHandler __init__ exc func=> %s" % fn.__name__)

    # 解析用户请求web.Request获取内部封装的参数转换为定义的视图函数的参数并调用视图函数
    # 规定视图函数需要通过命名关键字参数或关键字参数接收用户数据参数
    # 路径match_info匹配的参数可以为位置参数，调用都是以关键字参数调用，即**kw
    # 1.定义kw，用于保存参数
    # 2.判断视图函数是否存在关键词参数，如果存在根据POST或者GET方法将request请求内容保存到kw
    # 3.如果kw为空（说明request无请求内容），则将match_info列表里的资源映射给kw；若不为空，把命名关键词参数内容给kw
    # 4.完善_has_request_arg和_required_kw_args属性
    async def __call__(self, request):
        kw = None
        logging.info("RequestHandler __call__ exc func=> %s,method=>%s " %
                     (self._func.__name__, request.method))
        # 若视图函数有命名关键字参数或关键字参数或request参数
        if self._has_var_kw_arg or self._has_named_kw_arg or self._has_request_arg:
            # 解析请求中的参数
            if request.method == 'POST':
                # 解析POST中的参数
                if not request.content_type:
                    return web.HTTPBadRequest('Missing Content-type')
                ct = request.content_type.lower()
                if ct.startswith('application/json'):
                    params = await request.json()  # 仅解析body字段的json数据
                    if not isinstance(params, dict):
                        return web.HTTPBadRequest('Json body must be a object')
                    kw = params
                # form表单请求的编码形式
                elif ct.startswith('application/x-www-form-urlencode') or ct.startswith('multipart/form-data'):
                    params = await request.post()  # 返回post的内容中解析后的数据。dict-like对象。
                    kw = dict(**params)  # 组成dict，统一kw格式
                else:
                    return web.HTTPBadRequest('Unsupported Content-Type: %s' % request.content_type)
            if request.method == "GET":
                # 解析GET请求参数
                qs = request.query_string  # 返回URL查询语句,?后的键值.string形式
                if qs:
                    kw = dict()
                    # 返回查询变量和值的映射,dict对象.True表示不忽略空格.
                    qsObj = parse.parse_qs(qs, True)
                    logging.info("---- qs => %s,qsObj=>%s " % (qs, qsObj))
                    for k, v in qsObj.items():
                        kw[k] = v[0]
        # 若request中无参数
        # request.match_info返回dict对象。可变路由中的可变字段{variable}为参数名，传入request请求的path为值
        # 若存在可变路由：/a/{name}/c，可匹配path为：/a/jack/c的request
        # 则request.match_info返回{name = jack}
        if kw is None:
            kw = dict(**request.match_info)
        else:
            # 若视图函数只有命名关键字参数没有关键字参数
            # 则过滤接收的参数与视图函数的参数进行匹配
            if not self._has_var_kw_arg and self._has_named_kw_arg:
                copy = dict()
                for name in self._named_kw_args:
                    if name in kw:
                        copy[name] = kw[name]
                kw = copy
            # 将request.match_info中的参数传入kw
            for k, v in request.match_info.items():
                # 检查kw中的参数是否和match_info中的重复
                if k in kw:
                    logging.warning(
                        'Duplicating arg name in named arg and kw args: %s ' % k)
                kw[k] = v
        if self._has_request_arg:  # 视图函数存在request参数
            kw['request'] = request
        if self._required_kw_arg:  # 视图函数存在无默认值的命名关键词参数
            for name in self._required_kw_arg:
                if name not in kw:  # 若未传入必须参数值，报错
                    return web.HTTPBadRequest('Missing argment:%s' % name)
        # 至此，kw为视图函数fn真正能调用的参数
        # request请求中的参数，终于传递给了视图函数
        logging.info('call with args:%s' % str(kw))
        try:
            logging.info('self._func name=>%s kw=> %s ' %
                         (self._func.__name__, str(kw)))
            r = await self._func(**kw)
            return r
        except APIError as e:
            return dict(error=e.error, data=e.data, message=e.message)


# 编写一个add_route函数，用来注册一个视图函数
def add_route(app, fn):
    method = getattr(fn, '__method__', None)
    path = getattr(fn, '__route__', None)
    if path is None or method is None:
        raise ValueError('@get or @post not defined in %s.' % str(fn))
    if not asyncio.iscoroutinefunction(fn) and not inspect.isgeneratorfunction(fn):
        fn = asyncio.coroutine(fn)
    logging.info('add route %s %s => %s(%s)' % (
        method, path, fn.__name__, ', '.join(inspect.signature(fn).parameters.keys())))
    app.router.add_route(method, path, RequestHandler(app, fn))


# 导入模块，批量注册视图函数
# 自动把handler模块的所有符合条件的函数注册了
def add_routes(app, module_name):
    n = module_name.rfind('.')  # 从右侧检索，返回索引。若无，返回-1
    if n == (-1):
        # __import__ 作用同import语句，但__import__是一个函数，并且只接收字符串作为参数
        # __import__('os',globals(),locals(),['path','pip'], 0) ,等价于from os
        # import path, pip
        mod = __import__(module_name, globals(), locals())
    else:
        name = module_name[n + 1:]
        mod = getattr(__import__(
            module_name[:n], globals(), locals(), [name]), name)
    for attr in dir(mod):
        if attr.startswith('_'):
            continue
        fn = getattr(mod, attr)
        if callable(fn):
            method = getattr(fn, '__method__', None)
            path = getattr(fn, '__route__', None)
            if method and path:
                add_route(app, fn)


# 添加静态文件，如image，css，javascript等
def add_static(app):
    # 拼接static文件目录
    path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'static')
    # path = os.path.join(os.path.abspath('.'), 'static')

    app.router.add_static('/static/', path)
    logging.info('add static %s => %s' % ('/static/', path))
